package org.haredot.web;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.io.IOUtils;
import org.haredot.annotation.RequestBody;
import org.haredot.annotation.RequestParam;
import org.haredot.annotation.RequestPart;
import org.haredot.constraint.SysConst;
import org.haredot.context.ApplicationContext;
import org.haredot.media.MediaType;
import org.haredot.utils.WebUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Part;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class RequestMapping {
    /**
     * 类对象
     */
    private Class<?> target;
    /**
     * 控制器中的方法
     */
    private Method method;


    private String urlPattern;

    /**
     * 方法支持的请求方式
     */
    private List<RequestMethod> requestMethods;

    public Class<?> getTarget() {
        return target;
    }

    public void setTarget(Class<?> target) {
        this.target = target;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public List<RequestMethod> getRequestMethods() {
        return requestMethods;
    }

    public void setRequestMethods(List<RequestMethod> requestMethods) {
        this.requestMethods = requestMethods;
    }

    public String getUrlPattern() {
        return urlPattern;
    }

    public void setUrlPattern(String urlPattern) {
        this.urlPattern = urlPattern;
    }

    public Object invokeExecute(HttpServletRequest request) {
        try {
            ApplicationContext ctx = (ApplicationContext) request.getSession().getServletContext().getAttribute(SysConst.APPLICATION_CONTEXT);
            Object bean = ctx.getBean(target);

            Annotation[][] parameterAnnotations = method.getParameterAnnotations();

            Type[] genericParameterTypes = method.getGenericParameterTypes();
            // 获取 请求方法对应的参数列表
            Class<?>[] parameterTypes = method.getParameterTypes();

            Object[] args = new Object[parameterTypes.length];

            // 遍历所有的参数类型，根据类型上的注解、准备注入数据
            int index = 0;
            for (Class<?> parameterType : parameterTypes) {
                // 获取该参数上的所有注解
                Annotation[] annotations = parameterAnnotations[index];
                Type genericParameterType = genericParameterTypes[index];
                // 根据注解，获取该注解在参数中的数据
                Object val = findDataByAnnotations(parameterType, genericParameterType, annotations, request);
                args[index++] = val;
            }

            return method.invoke(bean, args);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            Throwable targetException = e.getTargetException();
            throw new RuntimeException(targetException);
        }
    }

    /**
     * 根据一个参数中的所有注解，获取该注解对应的值
     *
     * @param parameterType
     * @param genericParameterType
     * @param annotations
     * @param request
     * @return
     */
    private Object findDataByAnnotations(Class<?> parameterType, Type genericParameterType, Annotation[] annotations, HttpServletRequest request) {
        // 如果 annotations 是空的， 判断 类型是否是 Web 中的 request, session,  application, response对象
        if (annotations.length == 0) {
            if (parameterType == HttpServletRequest.class) {
                return request;
            }
            if (parameterType == HttpServletResponse.class) {
                return WebUtils.getResponse();
            }
            if (parameterType == HttpSession.class) {
                return WebUtils.getSession();
            }
            if (parameterType == ServletContext.class) {
                return WebUtils.getApplication();
            }
            throw new RuntimeException(parameterType + "对应的参数无法找到对应的值");
        }
        // 如果 请求是 表单请求 ，且 包含 @RequestParam 注解
        String contentType = request.getContentType();
        if (contentType == null || "".equals(contentType))
            contentType = MediaType.APPLICATION_X_WWW_FORM_URLENCODED_STRING;
        MediaType mediaType = MediaType.of(contentType);

        // 如果请求是表单请求
        if ((Objects.equals(mediaType.getType(), MediaType.APPLICATION_X_WWW_FORM_URLENCODED_STRING)
                && Arrays.stream(annotations).anyMatch(an -> an.annotationType() == RequestParam.class)) ||
                (Objects.equals(mediaType.getType(), MediaType.MULTIPART_FORM_DATA_STRING) && Arrays.stream(annotations).anyMatch(an -> an.annotationType() == RequestParam.class))) {
            // 获取找到的 数据
            Optional<Annotation> first = Arrays.stream(annotations)
                    .filter(an -> an.annotationType() == RequestParam.class).findFirst();
            // 获取注解
            RequestParam requestParam = (RequestParam) first.get();
            // 获取 对应的 键
            String key = requestParam.value();

            // 从请求中获取 key 对应的 值
            String parameter = request.getParameter(key);

            if (parameter == null && requestParam.required()) {
                throw new RuntimeException("参数" + key + "无法获取，请检查是否在页面中传递该参数");
            }
            // 如果设置了默认值
            if (parameter == null && !"".equals(requestParam.defaultValue())) {
                parameter = requestParam.defaultValue();
            }
            // 判断参数的类型 ，并转换成对应的类型
            return convertStringToType(parameter, parameterType);
        } else if (Objects.equals(mediaType.getType(), MediaType.MULTIPART_FORM_DATA_STRING)
                && Arrays.stream(annotations).anyMatch(an -> an.annotationType() == RequestPart.class)) {
            // 处理上传的文件
            if (parameterType != Part.class) {
                throw new RuntimeException("RequestPart注解标记的参数必须是javax.servlet.http.Part类型");
            }
            // 获取找到的 数据
            Optional<Annotation> first = Arrays.stream(annotations)
                    .filter(an -> an.annotationType() == RequestPart.class).findFirst();

            RequestPart requestParam = (RequestPart) first.get();
            // 获取 对应的 键
            String key = requestParam.value();
            try {
                Part part = request.getPart(key);
                return part;
            } catch (Exception e) {
                throw new RuntimeException("参数" + key + "对应的文件没有找到");
            }
        } else if (Objects.equals(mediaType.getType(), MediaType.APPLICATION_JSON_STRING)
                && Arrays.stream(annotations).anyMatch(an -> an.annotationType() == RequestBody.class)) {
            // 获取 流 中的数据，转换为 JSON 对象即可
            try {
                ServletInputStream inputStream = request.getInputStream();
                byte[] bytes = IOUtils.toByteArray(inputStream);
                if (parameterType != List.class) {
                    return JSONObject.parseObject(new String(bytes, mediaType.getCharset()), parameterType);
                } else {
                    if (genericParameterType instanceof ParameterizedType type) {
                        Class<?> aClass = (Class<?>) type.getActualTypeArguments()[0];
                        return JSONArray.parseArray(new String(bytes, mediaType.getCharset()), aClass);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException("request.getInputStream() 无法获取 application/json 请求中的数据");
            }
        }
        throw new RuntimeException(parameterType + "对应的参数无法找到对应的值");
    }

    private Object convertStringToType(String value, Class<?> targetType) {
        if (value == null) return null;
        try {
            if (targetType == String.class) return value ;
            // 获取转换方法
            Method method = targetType.getMethod("valueOf", String.class);
            // 调用转换方法并返回结果
            return method.invoke(null, value);
        } catch (Exception e) {
            throw new RuntimeException("无法将值【" + value + "】转换为" + targetType);
        }
    }

}
